package com.gaff.emp.core.syslog.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.gaff.emp.core.R;
import com.gaff.emp.core.syslog.annotation.SysOperaLog;
import com.gaff.emp.core.syslog.event.SysLogEvent;
import com.gaff.emp.core.modules.auth.LoginUserInfo;
import com.gaff.emp.core.modules.auth.interceptor.AuthorizationInterceptor;
import com.gaff.emp.core.modules.sys.entity.SysLog;
import com.gaff.emp.core.util.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.time.Instant;
import java.util.Arrays;
import java.util.Objects;


/**
 * 系统日志，切面处理类
 */
@Slf4j
@Aspect
@Component
public class SysLogAspect implements ApplicationEventPublisherAware {

    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Pointcut("@annotation(com.gaff.emp.core.syslog.annotation.SysOperaLog)")
    public void logPointCut() {

    }

    @Before(value = "logPointCut()")
    public void recordLog(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLog sysLog = new SysLog();
        //将当前实体保存到threadLocal
        sysLogThreadLocal.set(sysLog);

        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        LoginUserInfo userInfo = (LoginUserInfo) request.getAttribute(AuthorizationInterceptor.USER_INFO_KEY);
        if (userInfo != null) {
            sysLog.setUserId(userInfo.getUser().getId());
            sysLog.setUsername(userInfo.getUser().getUsername());
        } else {
            sysLog.setUserId(-1);
            sysLog.setUsername("未知");
        }
        sysLog.setIp(IPUtil.getIPFromRequest());

        SysOperaLog sysOperaLog = method.getAnnotation(SysOperaLog.class);

        if (sysOperaLog != null) {
            sysLog.setOperation(sysOperaLog.value());
            sysLog.setPersistence(sysOperaLog.isPersistence());
        }

        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");

        // 访问目标方法的参数 可动态改变参数值
        Object[] args = joinPoint.getArgs();
        sysLog.setParams(Arrays.toString(args));
    }

    @AfterReturning(returning = "ret", pointcut = "logPointCut()")
    public void doAfterReturning(Object ret) {
        SysLog sysLog = sysLogThreadLocal.get();
        R r = Convert.convert(R.class, ret);
        if (r == null) {
            sysLog.setType(1);
            sysLog.setConsumingTime((int) (Instant.now().toEpochMilli() - sysLog.getStartTime()));
            applicationEventPublisher.publishEvent(new SysLogEvent(sysLog));
            sysLogThreadLocal.remove();
            return;
        }
        if (r.getCode() == 200) {
            sysLog.setType(1);
        } else {
            sysLog.setType(2);
            sysLog.setExDetail(r.getMsg());
        }
        sysLog.setConsumingTime((int) (Instant.now().toEpochMilli() - sysLog.getStartTime()));
        applicationEventPublisher.publishEvent(new SysLogEvent(sysLog));
        sysLogThreadLocal.remove();
    }

    @AfterThrowing(pointcut = "logPointCut()", throwing = "e")
    public void doAfterThrowable(Throwable e) {
        SysLog sysLog = sysLogThreadLocal.get();
        sysLog.setType(2);
        sysLog.setExDetail(getStackTrace(e));
        sysLog.setExDesc(e.getMessage());
        sysLog.setConsumingTime((int) (Instant.now().toEpochMilli() - sysLog.getStartTime()));
        applicationEventPublisher.publishEvent(new SysLogEvent(sysLog));
        sysLogThreadLocal.remove();
    }

    public static String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            return sw.toString();
        }
    }
}
